#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <asm/errno.h>
#include <linux/uaccess.h>

#define ALLOC_DEV_ID		1 // 自动生成设备号

#define VIRTUAL_MEM_SIZE	4096 // global memory / scull 内存长度

static void *virtual_memory; // global memory / scull 内存指针
static unsigned long data_len; // global memory / scull 可读去的数据长度

static int major, minor;
static dev_t virtual_dev_id;
static struct cdev virtual_cdev;
static struct class *virtual_class;
static struct device *virtual_device;

enum { // ioctl 控制命令
	IOCTL_CMD_CLEAR, // 清空global memory / scull
	IOCTL_CMD_GET_VAL, // 读取数据
	IOCTL_CMD_SET_VAL, // 写入数据
	_IOCTL_CMD_MAX,
};

static int virtual_open(struct inode *inode, struct file *filp)
{
	printk("======= In function: %s\n", __FUNCTION__);

	return 0;
}

static int virtual_release(struct inode *inode, struct file *filp)
{
	printk("======= In function: %s\n", __FUNCTION__);

	return 0;
}

static ssize_t virtual_read(struct file *filp, char __user *buf, size_t bufsize, loff_t *offset)
{
	int ret = 0;
	unsigned int copy_length;

	copy_length = (bufsize < data_len) ? bufsize : data_len; // 防止内存越界访问
	if (data_len > 0) {
		ret = copy_to_user(buf, virtual_memory, copy_length); // 将可读的数据复制给用户空间内存
		if (ret < 0) {
			printk("Read failed.\n");
			goto err_0;
		}

		data_len -= copy_length;
		ret = copy_length;
	}

	printk("======= In function: %s\n", __FUNCTION__);
	
err_0:
	return ret;
}

static ssize_t virtual_write(struct file *filp, const char __user *buf, size_t bufsize, loff_t *offset)
{
	int ret = 0;
	unsigned int copy_length;

	copy_length = (bufsize < VIRTUAL_MEM_SIZE) ? bufsize : VIRTUAL_MEM_SIZE; // 防止内存越界访问
	ret = copy_from_user(virtual_memory, buf, copy_length); // 复制用户数据到 global memory / scull
	if (ret < 0) {
		printk("Write fialed.\n");
		ret = -EFAULT;
		goto err_0;
	}

	data_len = copy_length;
	ret = copy_length;

	printk("======= In function: %s\n", __FUNCTION__);

err_0:
	return ret;
}

static long virtual_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
	int ret = 0;
	void *user_buf;

	user_buf = (void *)args;
	
	switch (cmd) {
		case IOCTL_CMD_CLEAR : {
			memset(virtual_memory, 0, VIRTUAL_MEM_SIZE);
			data_len = 0;
		} break;

		case IOCTL_CMD_GET_VAL : {
			ret = copy_to_user(user_buf, virtual_memory, VIRTUAL_MEM_SIZE);
		} break;

		case IOCTL_CMD_SET_VAL : {
			ret = copy_from_user(user_buf, virtual_memory, VIRTUAL_MEM_SIZE);
		} break;

		default : ret = -EINVAL;
	}

	printk("======= In function: %s\n", __FUNCTION__);

	return ret;
}

static loff_t virtual_llseek(struct file *filp, loff_t offset, int whence)
{
	loff_t cur_offset;
	loff_t ret = 0;
	

	switch (whence) {
		case SEEK_SET : {
			cur_offset = offset;
		} break;

		case SEEK_CUR : {
			cur_offset = filp->f_pos;
			cur_offset += offset;
		} break;

		case SEEK_END : {
			cur_offset = data_len;
			cur_offset += offset;
		} break;

		default : {
			ret = -EINVAL;
			goto err_0;
		}
	}

	if ((cur_offset >= 0) && (cur_offset <= VIRTUAL_MEM_SIZE)) {
		filp->f_pos = cur_offset;
		ret = cur_offset;
	} else {
		ret = filp->f_pos;
	}

err_0:
	printk("======= In function: %s\n", __FUNCTION__);

	return ret;
}

static struct file_operations virtual_file_ops = {
	.open		= virtual_open,
	.release	= virtual_release, // 对应用户空间的close()
	.read		= virtual_read,
	.write		= virtual_write,
	.unlocked_ioctl	= virtual_unlocked_ioctl, // 对应用户空间的ioctl()
	.llseek		= virtual_llseek, // 对应用户空间的lseek()
};

static int __init virtual_init(void)
{
	int ret;

	printk("======= In function: %s\n", __FUNCTION__);

	virtual_memory = kzalloc(VIRTUAL_MEM_SIZE, GFP_KERNEL); // 为global memory / scull 申请内存
	if (virtual_memory == NULL) {
		printk("alloc memery failed.\n");
		ret = -ENOMEM;
		goto err_0;
	}

	data_len = 0;

#if ALLOC_DEV_ID
	ret = alloc_chrdev_region(&virtual_dev_id, 0, 1, "virtual_device");
	if (ret < 0) {
		printk("regist device id failed.\n");
		goto err_1;
	}

	major = MAJOR(virtual_dev_id);
	minor = MINOR(virtual_dev_id);
	printk("Major = %d, minor = %d\n", major, minor);
#else
	major = 123;
	minor = 0;
	virtual_dev_id = MKDEV(major, minor);
	ret = register_chrdev_region(virtual_dev_id, 1, "virtual_device");
	if (ret < 0) {
		printk("regist device id failed.\n");
		goto err_1;
	}
#endif

	cdev_init(&virtual_cdev, &virtual_file_ops);
	ret = cdev_add(&virtual_cdev, virtual_dev_id, 1);
	if (ret < 0) {
		printk("cdev add failed.\n");
		goto err_2;
	}

	virtual_class = class_create(THIS_MODULE, "virtual_devices");
	virtual_device = device_create(virtual_class, NULL, virtual_dev_id, NULL, "virtual_device_0");

	return 0;
err_2:
	unregister_chrdev_region(virtual_dev_id, 1);
err_1:
	kfree(virtual_memory);
err_0:
	return ret;
}

static void __exit virtual_exit(void)
{
	printk("======= In function: %s\n", __FUNCTION__);

	device_destroy(virtual_class, virtual_dev_id);
	class_destroy(virtual_class);
	cdev_del(&virtual_cdev);
	unregister_chrdev_region(virtual_dev_id, 1);
	kfree(virtual_memory); // 不释放的话，此内存会一直占用，直到系统重启或关机！
}

module_init(virtual_init);
module_exit(virtual_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYW");
